/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.ui.nodes;

import bgu.sonar.util.collections.CategoryMap;
import bgu.sonar.util.collections.obs.ObservableCategoryMap;
import bgu.sonar.util.collections.obs.ObservableList;
import bgu.sonar.util.collections.obs.ObservableMap;
import java.util.Collection;
import java.util.List;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;

/**
 *
 * @author bennyl
 */
public class CategoryNode extends ExtendableNode implements ObservableMap.MapListener, ObservableList.ListListener {

    private ValueNodeFactory factory;
    private ObservableCategoryMap rootCategory;

    /**
     *
     * @param cat
     * @param valueFactory can be null and in this case no value nodes will get created.
     */
    public CategoryNode(Object model, ObservableCategoryMap cat, ValueNodeFactory valueFactory) {
        super(model);
        this.rootCategory = cat;
        this.factory = valueFactory;
        CategoryNodeAttributes attributes = CategoryNodeAttributes.extract(rootCategory);
        if (attributes == null) {
            setName(rootCategory.getCategoryName());
            setIconBaseWithExtension("resources/img/blueFolder.png");
        } else {
            setAttributes(attributes);
        }
        adjustChildren();
        rootCategory.childrenMap().getListeners().add(this);
    }

    public CategoryNode(ObservableCategoryMap cat, ValueNodeFactory valueFactory) {
        this(extractCategoryModel(cat), cat, valueFactory);
    }

    public CategoryNode(ObservableCategoryMap cat) {
        this(cat, new DefaultValueNodeFactory());
    }

    private static Object extractCategoryModel(ObservableCategoryMap category) {
        StringBuilder sb = new StringBuilder("");
        for (String s : category.getCategory()) {
            sb.append(s).append(".");
        }

        return sb.delete(sb.length() - 1, sb.length()).toString();
    }

    public ObservableCategoryMap getCategoryMap() {
        return rootCategory;
    }

    @Override
    public void onValueAdded(ObservableMap src, Object key, Object value) {
        adjustChildren();
    }

    @Override
    public void onValueRemoved(ObservableMap src, Object key, Object value) {
        adjustChildren();
    }

    @Override
    public void onMapCleared(ObservableMap src) {
        adjustChildren();
    }

    private void adjustChildren() {
        if (this.rootCategory.children().isEmpty() && this.getChildren() != Children.LEAF) {
            setChildren(Children.LEAF);
        } else if (this.getChildren() == Children.LEAF) {
            if (this.rootCategory.children().size() > 0 || this.rootCategory.values().size() > 0) {
                setChildren(Children.create(new CategoryNodeChildren(), false));
            }
        }
    }

    @Override
    public void onItemsAdded(ObservableList list, Collection items, int pos) {
        adjustChildren();
    }

    @Override
    public void onItemsRemoved(ObservableList list, Collection items) {
        adjustChildren();
    }

    @Override
    public void onListChanged(ObservableList list) {
        adjustChildren();
    }

    private class CategoryNodeChildren extends ChildFactory implements ObservableList.ListListener, ObservableMap.MapListener {
        
        public CategoryNodeChildren() {
//            System.out.println("Category Children for: " + rootCategory.getCategoryName());
            rootCategory.values().getListeners().add(this);
            rootCategory.childrenMap().getListeners().add(this);
        }

        @Override
        protected boolean createKeys(List toPopulate) {
            //create sub categories 
            toPopulate.addAll(rootCategory.childrenMap().values());
            toPopulate.addAll(rootCategory.values());


            CategoryMap toRemove = rootCategory.getChildCategory("node.attributes");
            if (toRemove != null) {
                toPopulate.remove(toRemove);
//                System.out.println("Category Children For " + rootCategory.getCategoryName() + " Found node.attributes");
            }

            return true;
        }

        @Override
        protected Node createNodeForKey(Object key) {
            if (key instanceof ObservableCategoryMap) {
                ObservableCategoryMap okey = (ObservableCategoryMap) key;
                return new CategoryNode(okey, factory);
            } else {
                return factory.createFor(rootCategory, key);
            }
        }

        @Override
        public void onItemsAdded(ObservableList list, Collection items, int pos) {
            refresh(true);
        }

        @Override
        public void onItemsRemoved(ObservableList list, Collection items) {
            refresh(true);
        }

        @Override
        public void onListChanged(ObservableList list) {
            refresh(true);
        }

        @Override
        public void onValueAdded(ObservableMap src, Object key, Object value) {
//            System.out.println("Category Children For " + rootCategory.getCategoryName() + " Got Children update: " + key);
            if ("node.attributes".equals(key)) {
                for (Node child : getChildren().getNodes()) {
                    if (child instanceof ExtendableNode) {
                        ExtendableNode node = (ExtendableNode) child;
                        node.setAttributes((CategoryNodeAttributes) value);
                    }
                }
            }
            refresh(true);
        }

        @Override
        public void onValueRemoved(ObservableMap src, Object key, Object value) {
            refresh(true);
        }

        @Override
        public void onMapCleared(ObservableMap src) {
            refresh(true);
        }
    }

    public static interface ValueNodeFactory<V> {

        Node createFor(ObservableCategoryMap<V> category, V value);
    }

    public static class DefaultValueNodeFactory implements ValueNodeFactory {

        @Override
        public Node createFor(ObservableCategoryMap category, Object value) {
            ExtendableNode result;

            if (value instanceof Node) {
                return (Node) value;
            }

            CategoryNodeAttributes attributes = CategoryNodeAttributes.extract(category);
            if (attributes != null && attributes.getChildrenVisualData() != null && attributes.getChildrenVisualData().canProvideFor(value)) {
                result = new ExtendableNode(value, attributes.getChildrenVisualData());
                result.setActionProvider(attributes.getChildrenActionsProvider());
            } else {
                result = new ExtendableNode(value);
            }

            return result;
        }
    }
}
